Skip to content

Bind fan-out lineage vars in instance middleware#189

Merged
chris-colinsky merged 2 commits into
mainfrom
fix/fan-out-index-instance-middleware
Jun 25, 2026
Merged

Bind fan-out lineage vars in instance middleware#189
chris-colinsky merged 2 commits into
mainfrom
fix/fan-out-index-instance-middleware

Conversation

@chris-colinsky

Copy link
Copy Markdown
Member

What

current_fan_out_index() (and current_fan_out_index_chain()) returned None inside fan-out instance_middleware, even though the middleware is logically running inside a specific instance.

Root cause

The engine binds the fan-out lineage ContextVars per-node, inside the inner subgraph (compiled.py). But instance_middleware wraps the inner subgraph from the outside (fan_out.py), before any node runs, so the vars are still unset there. Node-level reads (node bodies, node middleware like failure-isolation, the provider) all work; only instance-level middleware saw None.

instance_middleware is a public, documented extension point (the add_fan_out_node signature; docs/concepts/middleware.md; the fan-out-with-retry example). Its documented use, RetryMiddleware, does not read the index, which is why this sat latent. Custom instance middleware that reads the index or calls set_invocation_metadata would see None.

Change

Bind the three lineage ContextVars (fan_out_index plus the per-depth index/branch chains) to the instance's child_context around the instance_middleware chain, via a _bind_instance_lineage context manager, resetting on exit. Mirrors what compiled.py already does per-node.

  • Binds only when there is instance middleware to read them (the inner nodes bind them otherwise), so the no-middleware path is unchanged.
  • Resets before the error-handling path below, so the per-instance checkpoint saves keep their existing context.
  • Sets both chains together so set_invocation_metadata's depth-aligned lineage view stays consistent.

No shipped behavior change: nothing in the codebase reads the index from instance middleware, and no fixture pins the current None.

Test plan

  • tests/unit/test_fan_out.py: test_instance_middleware_sees_fan_out_index (the index + chain are the instance's, per instance) and test_instance_middleware_lineage_reset_on_failure (the binding resets on the exception path). Both fail without the fix.
  • Full tests/ -> 1462 passed. ruff + pyright clean.

CHANGELOG entry under Unreleased (rides into v0.16.0).

current_fan_out_index() (and the lineage chains) returned None inside
fan-out instance_middleware: the engine binds those ContextVars per-node
inside the inner subgraph (compiled.py), but instance_middleware wraps
the subgraph from outside, before any node runs. The documented use
(RetryMiddleware) doesn't read the index, so it sat latent; custom
instance middleware reading the index or calling set_invocation_metadata
saw None.

Bind the three fan-out lineage ContextVars (fan_out_index + the per-depth
index/branch chains) to the instance's child_context around the
instance_middleware chain via a _bind_instance_lineage context manager,
resetting on exit. Bind only when there is instance middleware to read
them (the inner nodes bind them otherwise), so the no-middleware path is
unchanged. Reset before the error-handling below so its saves keep their
existing context.
Copilot AI review requested due to automatic review settings June 25, 2026 00:02

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes fan-out instance_middleware seeing unset fan-out lineage ContextVars by binding the instance’s lineage (fan_out_index, index chain, branch chain) around the instance-middleware chain in fan_out.py, matching the per-node binding already done deeper in execution.

Changes:

  • Add _bind_instance_lineage(...) context manager and apply it around the instance_middleware chain execution in fan-out instances.
  • Add unit tests asserting instance_middleware can read current_fan_out_index() / current_fan_out_index_chain() and that lineage is reset on failure.
  • Document the fix in CHANGELOG.md under Unreleased.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
src/openarmature/graph/fan_out.py Bind/reset fan-out lineage ContextVars around instance_middleware execution for each fan-out instance.
tests/unit/test_fan_out.py Add regression tests for index visibility inside instance middleware and reset behavior on exceptions.
CHANGELOG.md Add Unreleased “Fixed” entry documenting the behavior correction.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tests/unit/test_fan_out.py Outdated
Comment thread tests/unit/test_fan_out.py Outdated
From CoPilot review of #189: expand the two new instance-middleware
tests' inner and parent graph construction from inline method chains to
the step-by-step named-builder pattern used throughout the module.
@chris-colinsky chris-colinsky merged commit efb19f5 into main Jun 25, 2026
6 checks passed
@chris-colinsky chris-colinsky deleted the fix/fan-out-index-instance-middleware branch June 25, 2026 00:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants